package tonytech.wordcode.controller;

import rx.Observable;
import rx.functions.Action0;
import rx.schedulers.Schedulers;
import tonytech.wordcode.mode.FileInfoImpl;
import tonytech.wordcode.service.*;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.function.Consumer;


public class DocCodeMegerController implements TonyServiceControllerImpl<DocCodeMegerController.DocCodeMegerContext, FileInfoImpl> {
    /**
     * 文档代码合并状态，包含需要扫描的文件夹和存储代码的word文件路径
     */
    public static class DocCodeMegerContext {
        private final String dirToBeStore;
        private final String fileToStoreCodes;

        public DocCodeMegerContext(String dirToBeStore, String fileToStoreCodes) {
            this.dirToBeStore = dirToBeStore;
            this.fileToStoreCodes = fileToStoreCodes;
        }

        public String getDirToBeStore() {
            return dirToBeStore;
        }

        public String getFileToStoreCodes() {
            return fileToStoreCodes;
        }
    }
    // 文件名必须包含的后缀名
    private static final String usefulSuffix = "docx";

    @Override
    public void service(DocCodeMegerContext context, Consumer<FileInfoImpl> consumer, Consumer<Throwable> onError, Action0 onCompleted) {
        if (!testFileUseful(new File(context.fileToStoreCodes))) {
            errorHandler(new Exception("doc isn't useful"), "请输入正确的word路径");
            return;
        }
        // 获得搜索输入文件夹所获取的文件队列
        List<List<File>> searchedRes = searchDirectoryAndGetSearchedFiles(context.getDirToBeStore());
        // 按顺序发送文件
        Observable.from(searchedRes)
                .observeOn(Schedulers.computation())
                .concatMap(Observable::from)
                .observeOn(Schedulers.io())
                .map(file -> {
                    // 注入文件扫描类
                    FileInfoScannerServicempl scanner = ServiceFactory.INSTANCE.getFileContentScanner(file);
                    FileInfoImpl mode;
                    try {
                        // 提取文件信息
                        scanner.buildFileInfo();
                    } catch (IOException e) {
                        errorHandler(e, file.getAbsolutePath() + " can't be readed");
                    }
                    mode = scanner.getMode();
                    return mode;
                })
                .observeOn(Schedulers.io())
                // 过滤无效文件
                .filter(fileInfo -> !fileInfo.getContent().isEmpty())
                .subscribe(consumer::accept, onError::accept, onCompleted);
    }

    /**
     * 检查文件是否有用
     * @param file 价差文件是否是.docx为后缀且存在的文件
     * @return true 如果文件可以用于存储，否则则为false
     */
    private boolean testFileUseful(File file) {
        return file.getName().split("\\.")[1].equals(usefulSuffix)
                && file.exists();
    }

    /**
     * 搜索输入的文件夹路径，并获取搜索到的所有文件，一个文件夹获取一个List<File>对象
     * @param path 待搜索的文件夹路径
     * @return 搜索到的所有文件，出错则返回空list
     */
    private List<List<File>> searchDirectoryAndGetSearchedFiles(String path) {
        List<List<File>> searchedResult;
        try {
            // future类型变量，阻塞直到返回结果
            searchedResult = getFutureFilesInChildDir(new File(path)).get();
        } catch (InterruptedException | ExecutionException e) {
            errorHandler(e, path + " can't be successfully searched");
            searchedResult = new ArrayList<>(0);
        }
        return searchedResult;
    }

    /**
     * 由Controller本身提供onError，onComplted，onNext
     * @param context 环境类
     */
    @Override
    public void service(DocCodeMegerContext context) {
        // 获取注入的文档写入对象
        final DocWriterServiceImpl docWriterService
                = ServiceFactory.INSTANCE.getDocWriter(
                new File(context.getFileToStoreCodes())
        );
        // 用来控制函数执行结束
        final CountDownLatch latch = new CountDownLatch(1);
        service(
                context,
                mode -> {
                    mode.getContent().forEach(
                            docWriterService::writeWord
                    );
                    System.out.println(mode.getFile().getAbsoluteFile()+" written");
                },
                Throwable::printStackTrace,
                ()->{
                    docWriterService.closeDoc();
                    latch.countDown();
                }
        );
        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void errorHandler(Exception e, String errorMessage) {
        e.printStackTrace();
        System.out.println(errorMessage);
    }

    /**
     * 获取子类的文件类型
     * @param dir 待搜索子文件夹
     * @return future类型的子文件夹可能获得的File列表
     */
    private Future<List<List<File>>> getFutureFilesInChildDir(final File dir) {
        return getFileSearcherObservableFromDir(dir)
                .toList()
                .toBlocking()
                .toFuture();
    }

    /**
     * 返回有一个搜索文件夹File的Observable
     * @param searchedDir 待搜索的File类型文件夹
     * @return 搜索文件夹下的File集合的Observable
     */
    private Observable<List<File>> getFileSearcherObservableFromDir(final File searchedDir) {
        return Observable.just(searchedDir)
                .observeOn(Schedulers.io())
                .concatMap(rootDir -> {
                    // 注入文件夹扫描服务类
                    AbstractDirectoryScannerService scanner = ServiceFactory.INSTANCE.getDirectoryScanner(rootDir);
                    // 扫描获得文件夹类
                    List<File> foundDirs = scanner.getDirectoriesInTheRootDirectoryList();
                    // 子文件下的文件集合容器
                    List<Future<List<List<File>>>> childDirFiles = new ArrayList<>();
                    // 在当前文件夹下的文件容器，每一个List对象对应一个文件夹下的File对象集合
                    List<List<File>> filesInSpecificDir = new ArrayList<>();
                    filesInSpecificDir.add(scanner.getFilesInTheRootDirectoryList());
                    // 扫描子文件夹，获取相应的future
                    foundDirs.forEach(foundDir -> childDirFiles.add(
                            getFutureFilesInChildDir(foundDir)
                    ));
                    // 获取每一个子文件夹下的文件
                    childDirFiles.forEach(
                            childFiles -> {
                                try {
                                    filesInSpecificDir.addAll(childFiles.get());
                                } catch (InterruptedException | ExecutionException e) {
                                    errorHandler(e, childFiles.toString() + " fail");
                                }
                            }
                    );
                    return Observable.from(filesInSpecificDir);
                });
    }


}
